package com.autumn.zero.workflow.services.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.activiti.engine.ProcessEngine;
import org.apache.commons.io.IOUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.springframework.beans.factory.annotation.Autowired;

import com.autumn.common.api.domain.entities.auditing.ISoftDelete;
import com.autumn.domain.repositories.IDefaultEntityRepository;
import com.autumn.mybatis.criterion.EntityQuery;
import com.autumn.util.EnvironmentConstants;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.StringUtils;
import com.autumn.util.data.DbAuditingUtils;
import com.autumn.zero.workflow.constants.ProcessCommonConstants;
import com.autumn.zero.workflow.constants.ProcessDesignStatusConstants;
import com.autumn.zero.workflow.entitys.AbstractWorkflowDefinition;
import com.autumn.zero.workflow.entitys.WorkflowForm;
import com.autumn.zero.workflow.listeners.ZeroExecutionListener;
import com.autumn.zero.workflow.listeners.ZeroTaskListener;
import com.autumn.zero.workflow.repositorys.IWorkflowFormRepository;
import com.autumn.zero.workflow.services.IWorkflowDefinitionServiceBase;

/**
 * 工作流定义抽象服务
 * 
 * @author 杨昌国 2018-12-24 14:12:11
 * @param <TWorkflowDefinition>
 * @param <TWorkflowDefinitionRepository>
 */
public abstract class AbstractWorkflowDefinitionService<TWorkflowDefinition extends AbstractWorkflowDefinition, TWorkflowDefinitionRepository extends IDefaultEntityRepository<TWorkflowDefinition>>
		implements IWorkflowDefinitionServiceBase<TWorkflowDefinition> {

	/**
	 * 获取用户仓储
	 * 
	 * @return
	 */
	protected abstract TWorkflowDefinitionRepository getWorkflowDefinitionRepository();

	/**
	 * 用户实体类型
	 */
	private final Class<TWorkflowDefinition> workflowDefinitionEntityClass;

	/**
	 * 工作流表单类型
	 */
	@Autowired
	protected IWorkflowFormRepository workflowFormRepository;

	/**
	 * 流程引
	 */
	@Autowired
	protected ProcessEngine processEngine;

	/**
	 * 
	 * @param workflowDefinitionEntityClass
	 */
	public AbstractWorkflowDefinitionService(Class<TWorkflowDefinition> workflowDefinitionEntityClass) {
		this.workflowDefinitionEntityClass = workflowDefinitionEntityClass;
	}

	/**
	 * 
	 * @return
	 */
	public final Class<TWorkflowDefinition> getWorkflowDefinitionEntityClass() {
		return this.workflowDefinitionEntityClass;
	}

	/**
	 * 是否是软删除实体
	 * 
	 * @return
	 */
	public final boolean isSoftDeleteWorkflowDefinitionEntity() {
		return ISoftDelete.class.isAssignableFrom(this.getWorkflowDefinitionEntityClass());
	}

	/**
	 * 系统条件处理
	 * 
	 * @param query 查询
	 */
	protected void systemByCriteria(EntityQuery<TWorkflowDefinition> query) {
		if (this.isSoftDeleteWorkflowDefinitionEntity()) {
			query.eq(ISoftDelete.FILED_IS_DELETE, false);
		}
	}

	private void checkEdit(TWorkflowDefinition workflowDefinition, boolean checkFormType, boolean checkName) {
		if (checkFormType) {
			EntityQuery<WorkflowForm> formQuery = new EntityQuery<>(WorkflowForm.class);
			formQuery.eq(WorkflowForm.FILED_FORM_TYPE, workflowDefinition.getFormType());
			if (!formQuery.exist(this.workflowFormRepository)) {
				ExceptionUtils.throwValidationException("指定的工作流表单类型不存在或不支持。");
			}
			EntityQuery<TWorkflowDefinition> query = new EntityQuery<>(this.getWorkflowDefinitionEntityClass());
			this.systemByCriteria(query);
			query.eq(AbstractWorkflowDefinition.FILED_FORM_TYPE, workflowDefinition.getFormType());
			if (query.exist(this.getWorkflowDefinitionRepository())) {
				ExceptionUtils.throwValidationException("指定的表单类型[" + workflowDefinition.getFormType() + "]已经重复。");
			}
		}
		if (checkName) {
			EntityQuery<TWorkflowDefinition> query = new EntityQuery<>(this.getWorkflowDefinitionEntityClass());
			this.systemByCriteria(query);
			query.eq(AbstractWorkflowDefinition.FILED_FLOW_NAME, workflowDefinition.getFlowName());
			if (query.exist(this.getWorkflowDefinitionRepository())) {
				ExceptionUtils.throwValidationException("指定的流程名称[" + workflowDefinition.getFlowName() + "]已经重复。");
			}
		}
	}

	/**
	 * 检查 xml 内容
	 * 
	 * @param def
	 * @return
	 */
	protected void checkXml(TWorkflowDefinition def) {
		if (StringUtils.isNullOrBlank(def.getProcessContent())) {
			def.setProcessContent("");
			return;
		}
		Document doc = null;
		try {
			doc = DocumentHelper.parseText(def.getProcessContent());
		} catch (DocumentException e) {
			ExceptionUtils.throwValidationException("指定的流程xml格式不正确。");
		}

		Element element = doc.getRootElement();
		Element processElement = element.element("process");
		if (processElement == null) {
			ExceptionUtils.throwValidationException("指定的流程xml格式不正确，无 process 节点。");
		}
		this.addOrUpdateXmlAttribute(processElement, "id", def.getProcessId());
		this.addOrUpdateXmlAttribute(processElement, "name", def.getFlowName());

		Element startEvent = processElement.element("startEvent");
		if (startEvent == null) {
			ExceptionUtils.throwValidationException("指定的流程xml格式不正确，无 startEvent 节点。");
		}

		Element endEvent = processElement.element("endEvent");
		if (endEvent == null) {
			ExceptionUtils.throwValidationException("指定的流程xml格式不正确，无 endEvent 节点。");
		}

		Element bpmnDiagram = element.element("BPMNDiagram");
		if (bpmnDiagram == null) {
			ExceptionUtils.throwValidationException("指定的流程xml格式不正确，无 bpmndi:BPMNDiagram 节点");
		}
		this.addOrUpdateXmlAttribute(bpmnDiagram, "id", "BPMNDiagram_" + def.getProcessId());
		Element bpmnPlane = bpmnDiagram.element("BPMNPlane");
		if (bpmnPlane == null) {
			ExceptionUtils.throwValidationException("指定的流程xml格式不正确，无 bpmndi:BPMNDiagram/bpmndi:BPMNPlane 节点");
		}
		this.addOrUpdateXmlAttribute(bpmnPlane, "bpmnElement", def.getProcessId());
		this.addOrUpdateXmlAttribute(bpmnPlane, "id", "BPMNPlane_" + def.getProcessId());
		// 移除之前所有监听
		this.removeListener(processElement);
		// 添加所有监听
		this.addAllListener(processElement);
		def.setProcessContent(this.outputXml(doc));
	}

	/**
	 * 输出 Xml
	 * 
	 * @param doc
	 * @return
	 */
	protected String outputXml(Document doc) {
//      Element root = doc.getRootElement();  
		// 格式化XML
		OutputFormat format = new OutputFormat();
		// 设置元素是否有子节点都输出
		format.setExpandEmptyElements(true);
		// 设置输出XML声明，不输出为 true
		format.setSuppressDeclaration(false);
		format.setIndent(true);
		format.setIndentSize(4);
		format.setLineSeparator(EnvironmentConstants.LINE_SEPARATOR);
		format.setTrimText(true);
		format.setNewlines(true);
		OutputStream outputStream = new ByteArrayOutputStream();
		XMLWriter writer = null;
		try {
			try {
				writer = new XMLWriter(outputStream, format);
				writer.write(doc);
				writer.close();
			} catch (IOException e) {
				throw ExceptionUtils.throwApplicationException(e.getMessage());
			}
			String res = outputStream.toString();
			return res;
		} finally {
			IOUtils.closeQuietly(outputStream);
		}
	}

	/**
	 * 添加监听
	 * 
	 * @param processElement 元素
	 */
	@SuppressWarnings({ "rawtypes" })
	protected void addAllListener(Element processElement) {
		List lst = processElement.elements();
		for (Object obj : lst) {
			if (obj instanceof Element) {
				Element element = (Element) obj;
				this.addListener(element);
			}
		}
		// 不能添加根级，会造成节点为 null 现象
		// this.addListener(processElement);
	}

	/**
	 * 添加监听
	 * 
	 * @param element 元素
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void addListener(Element element) {
		List lst = element.elements();
		Object[] nodes = new Object[lst.size()];
		nodes = lst.toArray(nodes);
		List<Element> elements = new ArrayList<>();
		// 复制并移出节点
		for (Object obj : nodes) {
			if (obj instanceof Element) {
				Element subElement = (Element) obj;
				elements.add(subElement);
				element.remove(subElement);
			}
		}

		// 在所有元素之前添加
		Element extensionElements = element.addElement("extensionElements");
		if (element.getName().equals("userTask")) {
			this.addListenerElement(extensionElements, "activiti:taskListener", "all", ZeroTaskListener.class);
		} else if (element.getName().equals("sequenceFlow")) {
			this.addListenerElement(extensionElements, "activiti:executionListener", "take",
					ZeroExecutionListener.class);
		} else {
			this.addListenerElement(extensionElements, "activiti:executionListener", "start",
					ZeroExecutionListener.class);
			this.addListenerElement(extensionElements, "activiti:executionListener", "end",
					ZeroExecutionListener.class);
		}

		// 将之前移除的节点再添加回来
		for (Element subElement : elements) {
			element.add(subElement);
		}
	}

	private void addListenerElement(Element extensionElements, String elementType, String eventName,
			Class<?> eventClass) {
		Element listener = extensionElements.addElement(elementType);
		listener.addAttribute("event", eventName);
		listener.addAttribute("class", eventClass.getName());
	}

	/**
	 * 移除原有监听
	 * 
	 * @param element 无素
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void removeListener(Element element) {
		List lst = element.elements();
		if (lst == null || lst.size() == 0) {
			return;
		}
		Object[] nodes = new Object[lst.size()];
		nodes = lst.toArray(nodes);
		for (Object obj : nodes) {
			if (obj instanceof Element) {
				Element node = (Element) obj;
				if (node.getName().equalsIgnoreCase("extensionElements")) {
					element.remove(node);
				} else {
					this.removeListener(node);
				}
			}
		}
	}

	/**
	 * 添加或更新属性
	 * 
	 * @param element
	 * @param name
	 * @param value
	 */
	protected void addOrUpdateXmlAttribute(Element element, String name, String value) {
		Attribute attribute = element.attribute(name);
		if (attribute == null) {
			element.addAttribute(name, value);
		} else {
			attribute.setValue(value);
		}
	}

	@Override
	public void add(TWorkflowDefinition workflowDefinition) {
		ExceptionUtils.checkNotNull(workflowDefinition, "workflowDefinition");
		workflowDefinition.forNullToDefault();
		workflowDefinition.valid();
		DbAuditingUtils.insertSetProperty(workflowDefinition);
		workflowDefinition.setProcessId(ProcessCommonConstants.PROCESS_NAME_PREFIX + workflowDefinition.getFormType());
		this.checkEdit(workflowDefinition, true, true);
		this.checkXml(workflowDefinition);
		this.getWorkflowDefinitionRepository().insert(workflowDefinition);
	}

	@Override
	public void update(TWorkflowDefinition workflowDefinition) {
		ExceptionUtils.checkNotNull(workflowDefinition, "workflowDefinition");
		workflowDefinition.forNullToDefault();
		workflowDefinition.valid();
		workflowDefinition.setProcessId(ProcessCommonConstants.PROCESS_NAME_PREFIX + workflowDefinition.getFormType());
		TWorkflowDefinition old = this.queryById(workflowDefinition.getId());
		if (old == null) {
			ExceptionUtils.throwValidationException("无法修改不存在的工作流。");
		}
		if (!old.getStatus().equals(ProcessDesignStatusConstants.NOT_PUBLISH)) {
			ExceptionUtils.throwValidationException("无法更新已发布或已卸载的工作流。");
		}
		workflowDefinition.setGmtCreate(old.getGmtCreate());
		workflowDefinition.setStatus(old.getStatus());
		DbAuditingUtils.updateSetProperty(workflowDefinition);
		boolean checkFormType = !old.getFormType().equals(workflowDefinition.getFormType());
		boolean checkNme = !old.getFlowName().equalsIgnoreCase(workflowDefinition.getFlowName().trim());
		this.checkEdit(workflowDefinition, checkFormType, checkNme);
		if (StringUtils.isNullOrBlank(workflowDefinition.getProcessContent())) {
			workflowDefinition.setProcessContent(old.getProcessContent());
		}
		this.checkXml(workflowDefinition);
		this.getWorkflowDefinitionRepository().update(workflowDefinition);
	}

	protected EntityQuery<TWorkflowDefinition> createQueryById(long id) {
		EntityQuery<TWorkflowDefinition> query = new EntityQuery<>(this.getWorkflowDefinitionEntityClass());
		this.systemByCriteria(query);
		query.eq(AbstractWorkflowDefinition.FILED_ID, id);
		return query;
	}

	@Override
	public TWorkflowDefinition queryById(long id) {
		EntityQuery<TWorkflowDefinition> query = this.createQueryById(id);
		return query.selectByFirst(this.getWorkflowDefinitionRepository());
	}

	@Override
	public void deleteById(long id) {
		TWorkflowDefinition entity = this.queryById(id);
		if (entity == null) {
			ExceptionUtils.throwAutumnException("无法删除不存在的记录。");
		}
		if (entity.getStatus().equals(ProcessDesignStatusConstants.PUBLISH)) {
			ExceptionUtils.throwAutumnException("无法删除已发布的流程。");
		}
		if (this.isSoftDeleteWorkflowDefinitionEntity()) {
			DbAuditingUtils.deleteSetProperty(entity);
			this.getWorkflowDefinitionRepository().update(entity);
		} else {
			EntityQuery<TWorkflowDefinition> query = this.createQueryById(id);
			query.deleteByWhere(this.getWorkflowDefinitionRepository());
		}
	}

	@Override
	public void updateByStatus(long id, int status) {
		if (!ProcessDesignStatusConstants.exist(status)) {
			ExceptionUtils.throwValidationException("无效的状态。");
		}
		TWorkflowDefinition workflowDefinition = this.queryById(id);
		if (workflowDefinition == null) {
			ExceptionUtils.throwValidationException("无法更新不存在的工作流。");
		}
		if (workflowDefinition.getStatus().equals(status)) {
			return;
		}
		workflowDefinition.setStatus(status);
		DbAuditingUtils.updateSetProperty(workflowDefinition);
		// 发布
		if (ProcessDesignStatusConstants.PUBLISH == status) {
			this.deploy(workflowDefinition);
		}
		this.getWorkflowDefinitionRepository().update(workflowDefinition);
	}

	/**
	 * 部署流程
	 * 
	 * @param workflowDefinition 工作流定义
	 */
	protected void deploy(TWorkflowDefinition workflowDefinition) {
		if (StringUtils.isNullOrBlank(workflowDefinition.getProcessContent())) {
			ExceptionUtils.throwValidationException("无法发布未配置流程的工作流。");
		}
		processEngine.getRepositoryService().createDeployment()
				.addString(workflowDefinition.getProcessId() + ".bpmn", workflowDefinition.getProcessContent())
				.name(workflowDefinition.getFlowName()).key(workflowDefinition.getProcessId()).deploy();
	}

	@Override
	public void updateByProcessContent(long id, String xml) {
		TWorkflowDefinition workflowDefinition = this.queryById(id);
		if (workflowDefinition == null) {
			ExceptionUtils.throwValidationException("无法更新不存在的工作流。");
		}
		if (!workflowDefinition.getStatus().equals(ProcessDesignStatusConstants.NOT_PUBLISH)) {
			ExceptionUtils.throwValidationException("无法更新已发布或已卸载的工作流。");
		}
		workflowDefinition.setProcessContent(xml);
		this.checkXml(workflowDefinition);
		DbAuditingUtils.updateSetProperty(workflowDefinition);
		this.getWorkflowDefinitionRepository().update(workflowDefinition);
	}
}
